package com.zhangyj.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.zhangyj.BoastException;
import com.zhangyj.cache.CacheEngine;
import com.zhangyj.cache.DataCacheManager;

public class SQLMacro implements Serializable {
	private static Pattern namedPattern = Pattern.compile("\\W:[a-zA-Z\\_\\#\\@]+[a-zA-Z0-9\\#\\@\\_\\.]*");
	private static final long serialVersionUID = -1293687535923225736L;
	private static final String CACHEKEY = "sql";
	public static final int TYPE_INT = 0;
	public static final int TYPE_STRINT = 1;
	public static final int TYPE_DECIMAL = 2;

	public static class Param implements Serializable {
		private static final long serialVersionUID = 8131649452112681191L;
		public String name = null;
		public int type = TYPE_STRINT;
		public String defaultValue = null;
	}

	public static SQLMacro getSQLMacro(String sqlName) {
		try {
			CacheEngine engine = DataCacheManager.getCacheEngine();
			SQLMacro sqlMacro = (SQLMacro) engine.getObject(CACHEKEY, sqlName);
			if (sqlMacro != null) {
				return sqlMacro;
			} else {
				synchronized (engine) {
					load();
				}
			}
			sqlMacro = (SQLMacro) engine.getObject(CACHEKEY, sqlName);
			if (sqlMacro == null) {
				throw new BoastException("The SQLMacro(" + sqlName + ") can't find!");
			}
			return sqlMacro;
		} catch (Exception e) {
			throw new BoastException(e);
		}
	}

	public static void load() throws Exception {
		CacheEngine engine = DataCacheManager.getCacheEngine();
		Reader re = new BufferedReader(new InputStreamReader(SQLMacro.class.getResourceAsStream("/sql.xml"), "utf-8"));
		SAXReader reader = new SAXReader();
		Document doc = reader.read(re);
		Element root = doc.getRootElement();
		Iterator<?> iterator = root.elementIterator();
		while (iterator.hasNext()) {
			SQLMacro s = new SQLMacro((Element) iterator.next());
			engine.putObject(CACHEKEY, s.name, s);
		}
		re.close();
	}

	private String name;
	private String resultSQL;
	private String resCountSQL;
	private List<Param> params;
	private String key = null;
	private SQLMacro(Element el) {
		this.name = el.attributeValue("name");
		Attribute attr = el.attribute("key");
		if (attr != null) {
			this.key  = attr.getText().trim().toLowerCase();
		}
		String where = initWhereSQL(el.element("where"));
		this.resultSQL =  el.elementText("result");
		if (where != null) {
			resultSQL += " " + where;
		}
		Element count = el.element("rescount");
		if (count != null) {
			this.resCountSQL = count.getText();
			if (where != null) {
				resCountSQL += " " + where;
			}
		}
		
		if (params != null && params.size() > 0) {
			initParam(el.element("params"));
		}
	}
	private String initWhereSQL(Element el) {
		if (el == null) {
			return null;
		}
		String sql = el.getText();
		if (sql != null && sql.indexOf(':') >= 0) {
			java.util.regex.Matcher m = namedPattern.matcher(sql);
			StringBuffer preparedSql = new StringBuffer();
			while (m.find()) {
				int varBeginPos = m.start();
				int varBeginPos2 = -1;
				char ch = 0;
				for (; varBeginPos < m.end(); varBeginPos++) {
					ch = sql.charAt(varBeginPos);
					if (varBeginPos2 == -1 && ch == ':')
						varBeginPos2 = varBeginPos;
					else if (varBeginPos2 >= 0 && ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || ch == '#' || ch == '@'))
						break;
				}
				String paramName = sql.substring(varBeginPos, m.end()).toLowerCase();
				m.appendReplacement(preparedSql, sql.substring(m.start(), varBeginPos2) + "?");
				Param param = new Param();
				param.name = paramName;

				if (params == null) {
					params = new ArrayList<Param>();
				}
				params.add(param);
			}
			m.appendTail(preparedSql);
			sql = preparedSql.toString();
		}
		return sql;
	}
	private void initParam(Element el) {
		if (el == null)
			return;
		for (Iterator<?> iter = el.elementIterator("param"); iter.hasNext();) {
			Element p = (Element) iter.next();
			String name = p.attributeValue("name");
			String type = p.attribute("type") != null ? p.attributeValue("type") : "string";
			String defaultValue = p.attribute("defaultValue") != null ? p.attributeValue("defaultValue") : null;
			for (int i = 0; i < this.params.size(); i++) {
				Param param = params.get(i);
				if (param.name.equals(name)) {
					param.type = pearsType(type);
					param.defaultValue = defaultValue;
					break;
				}
			}
		}
	}

	private int pearsType(String typename) {
		if ("int".equals(typename)) {
			return TYPE_INT;
		} else if ("decimal".equals(typename)) {
			return TYPE_DECIMAL;
		}
		return TYPE_STRINT;
	}

	public String getResultSql() {
		return resultSQL;
	}

	public String getResCountSQL() {
		return resCountSQL;
	}
	public List<Param> getParams() {
		return params;
	}

	public String getKey() {
		return key;
	}

	public static void main(String[] args) {
		SQLMacro sql = SQLMacro.getSQLMacro("clientList");
		System.out.println(sql.resultSQL);
	}
}
